import User from '../models/User'
import Track from '../models/Track'
import { type publishVideoConfigs, videoConfig } from '../common/config'
import { getToken } from '../index.api'
import store from 'store'
import userStore from '../store/index'
import { message } from 'ant-design-vue'

import QNRTC, {
  QNConnectionState,
  QNTrack as RTCTrack,
  QNRemoteUser as RTCUser,
  QNRTCClient,
  QNRemoteTrack,
  type QNConnectionDisconnectedInfo,
  QNConnectionDisconnectedReason,
  QNLocalTrack,
  QNRemoteVideoTrack,
  QNRemoteAudioTrack,
  type QNLocalAudioTrackStats,
  type QNLocalVideoTrackStats,
  QNScreenVideoTrack,
  QNLocalAudioTrack,
  QNLocalVideoTrack,
  QNVideoOptimizationMode,
  QNCameraVideoTrack,
} from 'qnweb-rtc'
import { ref } from 'vue'

export enum TrackCreateMode {
  'A' = '音频通话',
  'B' = '音视频通话',
  'C' = '音视频通话 + 屏幕共享',
  'D' = '屏幕共享 + 系统声音',
}

export class RoomStore {
  /** session.roomName */
  public roomid: string = ''

  /** session.roomToken */
  public token: string = ''

  /** appid */
  public appId: string = ''

  /** 前后置摄像头 */
  public facingMode: 'environment' | 'user' = 'user'

  /** 房间中的用户 */
  public users: Map<string, User> = new Map()

  /** 已选择要采集的 Track配置 */
  public selectedTrackCreateMode: TrackCreateMode = TrackCreateMode.B

  public screenConfig: '480p' | '720p' | '1080p' = '1080p'

  public videoDeviceId?: string = undefined

  public audioDeviceId?: string = undefined

  /** 已选择的清晰度 */
  public selectVideoConfig: keyof publishVideoConfigs = '480p'

  /** 已发布的 Track */
  public publishedTracks = ref<Map<string, Track>>(new Map())

  /** 已订阅的 Track Map */
  public subscribedTracks = ref<Map<string, Track>>(new Map())

  /** 当前房间连接状态 */
  public state: QNConnectionState = QNConnectionState.DISCONNECTED

  public callbackFn = (type: string) => {}

  /** 已发布的 AudioTrack */
  public publishedAudioTracks = (): Track[] => {
    return Array.from(this.publishedTracks.value.values()).filter((v) => v.rtcTrack.isAudio())
  }

  /** 已发布的 VideoTrack(Camera) */

  public publishedCameraTracks = (): Track[] => {
    return Array.from(this.publishedTracks.value.values()).filter(
      (v) => v.rtcTrack.tag === 'camera' || v.rtcTrack.tag === 'screen',
    )
  }

  /** 已发布的 VideoTrack(Screen) */
  public publishedScreenTracks = (): Track[] => {
    return Array.from(this.publishedTracks.value.values()).filter(
      (v) => v.rtcTrack.tag === 'screen',
    )
  }

  public setSelectedTrackCreateMode = (mode: TrackCreateMode) => {
    this.selectedTrackCreateMode = mode
  }

  public setScreenConfig = (config: '480p' | '720p' | '1080p') => {
    this.screenConfig = config
  }

  /** 切换已发布的 VideoTrack(Camera) Mute状态 */

  public toggleMutePublishedCamera = () => {
    this.publishedCameraTracks().forEach((track) => track.toggleMute())
  }

  /** 切换已发布的 VideoTrack(Screen) Mute状态 */

  public toggleMutePublishedScreen = () => {
    this.publishedScreenTracks().forEach((track) => track.toggleMute())
  }

  /** 切换前后置摄像头 */

  public toggleCameraFacingMode = async (ref: HTMLDivElement) => {
    this.setFaceingMode(this.facingMode === 'user' ? 'environment' : 'user')
    const tracks = Array.from(this.publishedTracks.value.values()).map(
      (t) => t.rtcTrack as QNLocalTrack,
    )
    const cameraTrack = tracks.find((item) => item instanceof QNCameraVideoTrack)
    if (cameraTrack) {
      await (cameraTrack as QNCameraVideoTrack).switchCamera()
      cameraTrack.play(ref, { mirror: false })
    }
  }
  /** 切换已发布的 AudioTrack Mute状态 */

  public toggleMutePublishedAudio = () => {
    this.publishedAudioTracks().forEach((track) => track.toggleMute())
  }

  /** 当前发布 Track 的实时状态，每隔 1 秒更新 */
  public publishTracksReport: {
    audio: QNLocalAudioTrackStats | null
    video: QNLocalVideoTrackStats | null
    screen: QNLocalVideoTrackStats | null
  } = { audio: null, video: null, screen: null }

  /** client */
  public session: QNRTCClient

  /** 房间内已发布的 Tracks */
  public remoteTracks = ref<Map<string, QNRemoteTrack>>(new Map())

  /** 采集的 sdk 中的 Track 离开房间释放用 */
  public localTracks: QNLocalTrack[] = []

  private statusInterval?: number

  /** 播放失败的 tracks 存到这里，后续引导点击播放 */
  public shouldResumedTracks: Track[] = []

  public showResumePlayDialog = (): boolean => {
    return this.shouldResumedTracks.length !== 0
  }

  constructor(callbackFn: (type: string) => void) {
    this.session = QNRTC.createClient()
    this.session.on('connection-state-changed', this.setState)
    this.session.on('user-joined', this.addUser)
    this.session.on('user-left', this.removeUser)
    this.callbackFn = callbackFn
    const selectVideoConfig = store.get('selectVideoConfig') as keyof publishVideoConfigs
    if (selectVideoConfig) {
      this.selectVideoConfig = selectVideoConfig
    }

    const storeAppId = store.get('qnrtnAppID')
    if (storeAppId) {
      this.setAppId(storeAppId)
    }
    window.onbeforeunload = () => {
      try {
        this.leaveRoom()
      } catch {}
    }
  }

  setFaceingMode = (type: 'user' | 'environment') => {
    this.facingMode = type
  }

  addShouldResumedTracks = (track: Track) => {
    this.shouldResumedTracks.push(track)
  }

  clearShouldResumedTracks = () => {
    this.shouldResumedTracks = []
  }

  playShouldResumedTracks = () => {
    for (const t of this.shouldResumedTracks) {
      if (t.rtcTrack && t.rtcTrack.mediaElement) {
        t.rtcTrack.mediaElement.play()
      }
    }
    this.clearShouldResumedTracks()
  }

  public setroomId = (roomId: string) => {
    this.roomid = roomId
  }

  public setToken = (token: string) => {
    this.token = token
  }

  public setLocalTracks = (tracks: QNLocalTrack[]) => {
    this.localTracks = tracks
  }

  public fetchRoomToken = async (): Promise<string> => {
    const userid = userStore.id
    if (!userid || !this.roomid) return ''
    const token: string = await getToken(this.appId, this.id, userid)
    this.token = token
    return token
  }

  public setAppId = async (appid: string, isStore?: boolean) => {
    this.appId = appid
    if (isStore) {
      store.set('qnrtnAppID', appid)
    }
  }

  public setState = (state: QNConnectionState, info?: QNConnectionDisconnectedInfo): void => {
    this.state = state
    if (state !== QNConnectionState.DISCONNECTED) return
    if (!info) return
    switch (info.reason) {
      case QNConnectionDisconnectedReason.LEAVE: {
        return
      }
      case QNConnectionDisconnectedReason.KICKED_OUT: {
        this.leaveRoom()
        message.warning('断开连接：被请出房间')
        this.callbackFn('leave')
        return
      }
      case QNConnectionDisconnectedReason.ERROR: {
        this.leaveRoom()
        message.warning(
          `断开连接：错误： ErrorCode: ${info.errorCode}, ErorMessage: ${info.errorMessage}`,
        )
        return
      }
    }
  }

  public setVideoDeviceId = async (deviceId: string) => {
    this.videoDeviceId = deviceId
    await this.unpublish()
    // this.releaseLocalTracks()
    const rtcTracks = await this.getSelectTracks()
    await this.publish(rtcTracks)
  }

  public setAudioDeviceId = async (deviceId: string) => {
    this.audioDeviceId = deviceId
    await this.unpublish()
    // this.releaseLocalTracks()
    const rtcTracks = await this.getSelectTracks()
    await this.publish(rtcTracks)
  }

  private addUser = (userID: string): void => {
    const rtcuser = this.session.getRemoteUser(userID)
    if (!rtcuser) return
    if (this.users.has(rtcuser.userID)) return
    this.users.set(rtcuser.userID, new User(rtcuser))
  }

  private removeUser = (userID: string): void => {
    const user = this.users.get(userID)
    const subscribedTracks = Array.from(this.subscribedTracks.value.values())
      .filter((item) => item.userID === userID)
      .map((item) => item.trackID)
    this.unsubscribe(subscribedTracks)
    this.users.delete(userID)
  }

  private addTracks = (userID: string, tracks: QNRemoteTrack[]): void => {
    if (this.users.has(userID)) {
      const user = this.users.get(userID) as User
      for (const track of tracks) {
        user.addPublishedTrack(track)
        this.remoteTracks.value.set(track.trackID as string, track)
      }
    }
    this.subscribe(tracks.map((v) => v.trackID) as string[])
  }

  private removeTracks = (userID: string, tracks: QNRemoteTrack[]): void => {
    if (this.users.has(userID)) {
      const user = this.users.get(userID) as User
      for (const track of tracks) {
        user.removePublishedTrack(track)
        user.tracks.delete(track.trackID as string)
        this.remoteTracks.value.delete(track.trackID as string)
      }
    }
  }

  public joinRoom = async (
    token: string = this.token,
    userData?: string,
    role?: string,
  ): Promise<void> => {
    if (role !== 'live-streaming') {
      this.session.on('user-published', this.addTracks)
      this.session.on('user-unpublished', this.removeTracks)
    }
    this.subscribedTracks.value.clear()
    this.remoteTracks.value.clear()
    this.users.clear()

    if (!token) return
    await this.session.join(token, userData)
    // this.setAppId(this.session.appId as string);
    userStore.setIdNoStore(this.session.userID as string)
    if (this.roomid !== this.session.roomName) {
      this.roomid = this.session.roomName as string
    }
  }

  public publish = async (tracks: QNLocalTrack[] = []): Promise<void> => {
    try {
      await this.session.publish(tracks)
      for (const track of tracks) {
        if (track.trackID) this.publishedTracks.value.set(track.trackID, new Track(track))
      }
      if (this.statusInterval) {
        window.clearInterval(this.statusInterval)
      }
      // 修改后（正确）
      this.statusInterval = window.setInterval(() => this.updateTrackStatusReport(), 1000)
    } catch (e) {
      tracks.map((t) => t.destroy())
      throw e
    }
  }

  public unpublish = async (): Promise<void> => {
    const tracks = Array.from(this.publishedTracks.value.values()).map(
      (t) => t.rtcTrack as QNLocalTrack,
    )
    this.publishedTracks.value.clear()
    await this.session.unpublish(tracks)
    tracks.forEach((t) => t.destroy())
  }

  public setSelectVideoConfig = (config: keyof publishVideoConfigs) => {
    this.selectVideoConfig = config
    store.set('selectVideoConfig', config)
  }

  public getSelectTracks = async (): Promise<QNLocalTrack[]> => {
    const vConfig = videoConfig.find((c) => c.key === this.selectVideoConfig)
    if (!vConfig) throw new Error('Invalid video config: ' + this.selectVideoConfig)

    let tracks: QNLocalTrack[] = []
    switch (this.selectedTrackCreateMode) {
      // 音频通话
      case TrackCreateMode.A: {
        tracks = [
          await QNRTC.createMicrophoneAudioTrack({
            tag: 'microphone',
            microphoneId: this.audioDeviceId,
          }),
        ]
        break
      }
      // 音视频通话
      case TrackCreateMode.B: {
        tracks = await QNRTC.createMicrophoneAndCameraTracks(
          { tag: 'microphone', microphoneId: this.audioDeviceId, AEC: true },
          {
            tag: 'camera',
            cameraId: this.videoDeviceId,
            facingMode: this.facingMode,
            encoderConfig: {
              width: vConfig.config.video!.width,
              height: vConfig.config.video!.height,
              frameRate: vConfig.config.video!.frameRate,
              bitrate: vConfig.config.video!.bitrate,
            },
          },
        )
        break
      }
      // 音视频 + 屏幕共享
      case TrackCreateMode.C: {
        tracks = [
          (await QNRTC.createScreenVideoTrack({
            encoderConfig: this.screenConfig,
            screenVideoTag: 'screen',
            optimizationMode: QNVideoOptimizationMode.DETAIL,
          })) as QNScreenVideoTrack,
          ...(await QNRTC.createMicrophoneAndCameraTracks(
            { tag: 'microphone', microphoneId: this.audioDeviceId },
            {
              tag: 'camera',
              cameraId: this.videoDeviceId,
              facingMode: this.facingMode,
              encoderConfig: {
                width: vConfig.config.video!.width,
                height: vConfig.config.video!.height,
                frameRate: vConfig.config.video!.frameRate,
                bitrate: vConfig.config.video!.bitrate,
              },
            },
          )),
        ]
        break
      }
      // 屏幕共享 + 系统声音
      case TrackCreateMode.D: {
        const ts = await QNRTC.createScreenVideoTrack(
          {
            encoderConfig: this.screenConfig,
            optimizationMode: QNVideoOptimizationMode.DETAIL,
            screenAudioTag: 'system-audio',
            screenVideoTag: 'screen',
          },
          'auto',
        )
        if (Array.isArray(ts)) {
          tracks = ts
        } else {
          tracks = [ts]
        }
        break
      }
    }

    this.handleTrackEnded(tracks)
    this.setLocalTracks([...this.localTracks, ...tracks])
    return tracks
  }

  public handleTrackEnded = async (tracks: QNLocalTrack[]) => {
    for (const track of tracks) {
      if (track.tag === 'microphone') {
        track.on('ended', async () => {
          const tracks = [await QNRTC.createMicrophoneAudioTrack({ tag: 'microphone' })]
          this.handleTrackEnded(tracks)
          this.setLocalTracks([...this.localTracks, ...tracks])
          this.publish(tracks)
        })
      } else if (track.tag === 'screen') {
        track.on('ended', async () => {
          track.trackID && this.publishedTracks.value.delete(track.trackID)
        })
      }
    }
  }

  /** 订阅 */

  public subscribe = async (trackids: string[]): Promise<void> => {
    let targetTracks: QNRemoteTrack[] = []
    trackids.forEach((trackID) => {
      const targetTrack = this.remoteTracks.value.get(trackID)
      if (targetTrack) {
        targetTracks.push(targetTrack)
      }
    })
    let innerfunc
    const removePromise = new Promise<RTCTrack[]>((resovle, reject) => {
      innerfunc = (_: string, tracks: QNRemoteTrack[]) => {
        for (const track of tracks) {
          if (trackids.includes(track.trackID as string)) {
            const error = new Error('订阅失败，订阅的track已移除')
            reject(error)
          }
        }
      }
      this.session.on('user-unpublished', innerfunc)
    })
    try {
      const rtctracks = await Promise.race([removePromise, this.session.subscribe(targetTracks)])
      if (innerfunc) {
        this.session.off('user-unpublished', innerfunc)
      }
      if (Array.isArray(rtctracks)) return

      for (const rtctrack of [...rtctracks.videoTracks, ...rtctracks.audioTracks]) {
        const track = new Track(rtctrack)
        this.subscribedTracks.value.set(rtctrack.trackID as string, track)
        const user = this.users.get(rtctrack.userID as string)
        if (user) {
          user.tracks.set(rtctrack.trackID as string, track)
        }
      }
    } catch (e) {
      console.warn(e)
      throw e
    }
  }

  /** 取消订阅 */

  public unsubscribe = async (trackids: string[]): Promise<void> => {
    let targetTracks: QNRemoteTrack[] = []
    trackids.forEach((trackID) => {
      const targetTrack = this.remoteTracks.value.get(trackID)
      if (targetTrack) {
        targetTracks.push(targetTrack)
      }
    })
    await this.session.unsubscribe(targetTracks)
    for (const trackid of trackids) {
      const track = this.subscribedTracks.value.get(trackid)
      if (!track) {
        return
      }
      this.subscribedTracks.value.delete(trackid)
      const user = this.users.get(track.userID as string)
      if (user) {
        user.tracks.delete(trackid)
      }
    }
  }

  releaseLocalTracks = (): void => {
    if (this.localTracks.length === 0) return
    this.localTracks.forEach((t) => {
      t.destroy()
    })
    this.localTracks = []
  }

  /** 离开房间触发，释放所有房间内的 Track */

  public leaveRoom = async (): Promise<void> => {
    // 确保清除定时器
    if (this.statusInterval) {
      clearInterval(this.statusInterval)
      this.statusInterval = undefined // 清除引用
    }
    this.releaseLocalTracks()
    this.publishedTracks.value.forEach((t) => (t.rtcTrack as QNLocalTrack).destroy())
    this.publishedTracks.value.clear()
    this.subscribedTracks.value.clear()
    this.remoteTracks.value.clear()
    this.users.clear()
    this.token = ''
    this.roomid = ''
    if (this.statusInterval) clearInterval(this.statusInterval)
    this.session.leave()
  }

  // /** 每隔 1 秒获取当前发布 Track 的状态 */

  private updateTrackStatusReport = (): void => {
    // 增加连接状态检查
    if (this.state !== QNConnectionState.CONNECTED) {
      clearInterval(this.statusInterval)
      this.statusInterval = undefined
      return
    }
    const publishedTracksList = Array.from(this.publishedTracks.value.values())

    const audioTrack = publishedTracksList.find((t) => t.rtcTrack.isAudio())
    const videoTrack = publishedTracksList.find((t) => t.tag === 'camera')
    const screenTrack = publishedTracksList.find((t) => t.tag === 'screen')

    this.publishTracksReport.audio = null
    if (audioTrack) {
      this.publishTracksReport.audio = (audioTrack.rtcTrack as QNLocalAudioTrack).getStats()
    }

    this.publishTracksReport.video = null
    if (videoTrack) {
      this.publishTracksReport.video = (videoTrack.rtcTrack as QNLocalVideoTrack).getStats()[0]
    }

    this.publishTracksReport.screen = null
    if (screenTrack) {
      this.publishTracksReport.screen = (screenTrack.rtcTrack as QNLocalVideoTrack).getStats()[0]
    }
  }
}
